Skip to content

Read live bot state with the engine's own edict dump - #261

Merged
saworbit merged 1 commit into
mainfrom
lab-edicts-dump
Sep 5, 2026
Merged

Read live bot state with the engine's own edict dump#261
saworbit merged 1 commit into
mainfrom
lab-edicts-dump

Conversation

@saworbit

@saworbit saworbit commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Closes the adopt half of #255.

ED_PrintEdicts walks the progs field definitions, so edicts dumps every entity with all its non-default fields, ours included. Confirmed mid-match on dm4 at t 25.1:

EDICT 231  Carmack    ar_mode 2  ar_node entity 91   ar_goal entity 223
                      ar_enemy entity 237  health 72
EDICT 234  Romero     ar_node entity 53   ar_goal entity 219
                      ar_routegoal entity 219  ar_laststart entity 39
EDICT 237  Joe Rogan  ar_mode 2  ar_node entity 101  ar_enemy entity 231

That is the state every forensics session this week added a dprint and recompiled to read. The door freeze needed ar_door and ar_liftwait. The routefail storm needed ar_goal and ar_failstreak. #257's 144 s freeze still needs ar_liftwait and ar_hoptrain. All of it was one console command away the whole time.

Two parts

The tune whitelist admits edicts, edict <n> and edictcount, so they can be injected into a live match through the channel that already exists. All three only print, so they are as safe as status, which was already allowed. edict all still rejects.

tools/argus_edicts.py turns a dump back into something readable:

default edict pressure against the 600 ceiling, plus per-bot state ordered for "why is this stuck"
--field ar_node one value across every bot
--edict 234 one entity in full
--make-cfg 25 a config of wait lines and a trailing edicts, for +exec when there is no injection channel

The dm4 capture above was taken with --make-cfg.

The dump is lossy, which is worth knowing

A 236 edict dm4 dump lost four EDICT headers to the console. A free edict prints FREE and no fields, so a stray FREE after a populated block belongs to a header that never arrived. Claiming it marked a live bot as free and hid Romero from the output entirely, which cost an hour to find.

The parser now attributes FREE only to an empty block and reports how many headers went missing. Field values are intact; counts are approximate.

Not adopted

Recorded on #255 with measurements. host_timescale runs 2.9x but drops engagements per game minute from 23.4 to 7.0, so tapes would not compare to any baseline. sys_ticrate does nothing. Savegames are refused outright in multiplayer.

121 lab tests pass.

🤖 Generated with Claude Code

Closes the adopt half of #255.

ED_PrintEdicts walks the progs field definitions, so `edicts` dumps
every entity with all its non-default fields, ours included. Confirmed
mid-match on dm4 at t 25.1:

  EDICT 231  Carmack    ar_mode 2  ar_node entity 91  ar_goal entity 223
                        ar_enemy entity 237  health 72
  EDICT 234  Romero     ar_node entity 53  ar_goal entity 219
                        ar_routegoal entity 219  ar_laststart entity 39
  EDICT 237  Joe Rogan  ar_mode 2  ar_node entity 101  ar_enemy entity 231

That is the state every forensics session this week added a dprint and
recompiled to read. The door freeze needed ar_door and ar_liftwait. The
routefail storm needed ar_goal and ar_failstreak. #257's 144 s freeze
still needs ar_liftwait and ar_hoptrain. All of it was one console
command away the whole time.

TWO PARTS.

The tune whitelist admits edicts, edict <n> and edictcount, so they can
be injected into a live match through the channel that already exists.
All three only print, so they are as safe as status, which was already
allowed. `edict all` still rejects.

tools/argus_edicts.py turns a dump back into something readable: edict
pressure against the 600 ceiling, per-bot state in the order you want
it when something is stuck, --field to compare one value across bots,
--edict for one entity in full. With no injection channel, --make-cfg
writes a config of `wait` lines and a trailing edicts for +exec, which
is how the dm4 capture above was taken.

THE DUMP IS LOSSY, which cost an hour and is worth knowing. A 236
edict dm4 dump lost four EDICT headers to the console. A free edict
prints FREE and no fields, so a stray FREE after a populated block
belongs to a header that never arrived; claiming it marked a live bot
as free and hid Romero entirely. The parser now attributes FREE only to
an empty block and reports how many headers went missing. Field values
are intact, counts are approximate.

Not adopted here, and recorded on #255 with measurements: host_timescale
runs 2.9x but drops engagements per game minute from 23.4 to 7.0, and
sys_ticrate does nothing. Savegames are refused outright in multiplayer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T04:53:53.865206Z 02c91ee PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@saworbit
saworbit merged commit e90964a into main Sep 5, 2026
3 checks passed
@saworbit
saworbit deleted the lab-edicts-dump branch September 5, 2026 04:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02c91eed38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/argus_edicts.py
h = HEAD.match(line)
if h:
cur = int(h.group(1))
out.setdefault(cur, {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reset an edict when a later dump repeats its header

When a persistent match log contains an initial edicts dump followed by the newly supported edict <n> probe, the repeated header reuses the existing dictionary. Because the engine omits fields whose current value is zero, old values such as ar_liftwait, ar_goal, or ar_enemy survive and are reported as current; if the edict is now FREE, it is likewise mistaken for a dropped header. Replace the record at a repeated header, or scope parsing to the latest requested probe, rather than merging snapshots.

Useful? React with 👍 / 👎.

Comment thread tools/argus_edicts.py
Comment on lines +80 to +81
out.setdefault("lost", 0)
out["lost"] += 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include headerless free edicts in the pressure totals

When a FREE record loses its header, this branch increments lost but discards the entity entirely, while the summary calculates total and free counts only from eds. The checked-in dm4 capture consequently reports 236 edicts dumped, 236 in use, 0 free, even though its four headerless FREE records mean 240 total, 236 used, and 4 free. Since the default output is intended to report edict pressure, retain placeholder records or incorporate lost into the totals.

Useful? React with 👍 / 👎.

// as status.
Regex::new(
r"(?i)^(skill\s+[0-3]|fraglimit\s+\d{1,3}|timelimit\s+\d{1,3}|developer\s+[01]|deathmatch\s+1|map\s+[A-Za-z0-9_]+|scratch[1-4]\s+-?\d{1,6}|status|serverinfo)$",
r"(?i)^(skill\s+[0-3]|fraglimit\s+\d{1,3}|timelimit\s+\d{1,3}|developer\s+[01]|deathmatch\s+1|map\s+[A-Za-z0-9_]+|scratch[1-4]\s+-?\d{1,6}|status|serverinfo|edicts|edictcount|edict\s+\d{1,4})$",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Advertise the new commands in the MCP tune contract

Adding these alternatives only to the validator leaves the MCP-facing TuneArgs schema in server.rs and the rejection message in validate_tune listing the old command set, while knobs() also omits the dump commands. MCP clients and their models use that schema/help to determine valid inputs, so the new live-state workflow remains undiscoverable unless callers already know its implementation details; update those public descriptions alongside the whitelist.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant